from typing import Dict, List, Tuple, Union, Optional
import json
from collections import namedtuple
from datetime import datetime

import aioping
from settings import scheduler

from utils import UserAdditionData, AddingWebAddressToUser
import settings


async def ping_web_address(web_address: str) -> Tuple[bool, Union[str, Tuple]]:
    """ Пингуем сайт """

    PingResultPositive: Tuple = namedtuple('PingResultPositive', ['delay', 'date', 'web_address'])
    PingResultNegative: Tuple = namedtuple('PingResultNegative', ['status_text', 'date', 'web_address'])
    ping_date: str = datetime.now().strftime('%H:%M:%S %d-%m-%Y')

    try:
        delay: float = await aioping.ping(web_address)
        delay_millisecond: float = delay * 1000
        data = PingResultPositive(
            delay=delay_millisecond,
            date=ping_date,
            web_address=web_address
        )
        return (True, data)
    except Exception as error:
        data = PingResultNegative(
            status_text=str(error),
            date=ping_date,
            web_address=web_address
        )
        return (False, data)

async def ping_web_address_by_user(username: str) -> None:
    """ Пингуем веб-адрес от пользователя с актуальным указанным значением этого веб-адреса """

    def save_result(response) -> None:
        """ Сохраняем результат пинга """

        nonlocal user_object
        database_object: Dict = DatabaseServices().get_database_object()
        database_object[str(user_object['user']['id'])]['ping_history'].append(
            response[1]._asdict()
        )

        DatabaseServices().update_database(database_object)


    # ВЫШЕ ОПРЕДЕЛЕНИЕ ФУНКЦИЙ

    user_object: Dict = DatabaseServices().get_user_object_by_username(username)
    if user_object['is_web_address_added']:
        response = await ping_web_address(user_object['web_address'])
        save_result(response)


class DatabaseServices:
    """ Инструменты для работы с базой данных """

    def get_database_object(self) -> Dict:
        """ Получаем объект базы данных """

        with open(settings.PATH_TO_DATABASE) as file:
            return json.loads(file.read())


    def get_key_for_next_user(self) -> int:
        """ Получаем ключ для следующего пользователя """

        database_object: Dict = self.get_database_object()
        list_of_user_keys: List[int] = list(map(int, database_object.keys()))
        last_value_of_key: int = list_of_user_keys[-1]
        new_key_value: int = last_value_of_key + 1

        if new_key_value in list_of_user_keys:
            raise ValueError(f'Появился ключ для пользователя, который уже существует - {new_key_value}')

        return new_key_value


    def update_database(self, database_object: Dict) -> None:
        """ Обнавляем базу данных новыми данными """

        with open(settings.PATH_TO_DATABASE, 'w') as file:
            file.write(json.dumps(database_object))


    def add_user_to_db(self, user_data: UserAdditionData) -> None:
        """ Добавляем пользователя в бд """

        database_object: Dict = self.get_database_object()
        user_key: int = self.get_key_for_next_user()

        object_of_addition: Dict = {
            'user': {
                'username': user_data['username'],
                'chat_id': str(user_data['chat_id']),
                'id': user_key
            },
            'is_web_address_added': False,
            'web_address': '',
            'ping_history': []
        }

        database_object[user_key] = object_of_addition

        self.update_database(database_object)


    def verify_existence_of_user(self, username: str) -> bool:
        """ Проверяем существование пользователя """

        database_object: Dict = self.get_database_object()

        for key in database_object:
            if username == database_object[key]['user']['username']: return True

        return False


    def add_user_if_does_not_exist(self, user_data: UserAdditionData) -> None:
        """ Добавляем пользователя в бд если его не существует """

        is_user_exists: bool = self.verify_existence_of_user(user_data['username'])
        if not is_user_exists: self.add_user_to_db(user_data)


    def get_user_object_by_username(self, username: str) -> Dict:
        """ Получаем объект пользователя по имени """

        database_object: Dict = self.get_database_object()

        for key in database_object:
            if username == database_object[key]['user']['username']:
                return database_object[key]

        raise ValueError(f'Пользователя с ником {username} не существует')


    async def add_new_web_address_to_user(
        self,
        user_data: AddingWebAddressToUser
    ) -> Tuple[bool, str]:
        """ Добавляем новый веб адрес пользователю """

        status, _ = await ping_web_address(user_data['web_address'])
        if not status: return (status, 'Убедитесь в корректности адреса или попробуйте позже')

        database_object: Dict = self.get_database_object()

        user_object: Dict = self.get_user_object_by_username(
            user_data['username']
        )
        user_id: str = str(user_object['user']['id'])
        user_object['is_web_address_added'] = True
        user_object['web_address'] = user_data['web_address']
        database_object[user_id] = user_object

        self.update_database(database_object)

        return (True, 'Веб-адрес успешно добавлен')


    async def ping_web_address(self, username: str) -> None:
        """ Пингуем веб-адрес от конкретного пользователя и сохраняем результаты """

        user_object: Dict = self.get_user_object_by_username(username)

        scheduler.add_job(
            ping_web_address_by_user,
            'interval',
            seconds=60,
            args=(user_object['user']['username'],)
        )


    def change_format_ping_history(self, ping_history: List[Dict]) -> str:
        """ Изменяем вид списка с историй пингов """

        result: List[str] = []

        for ping_object in ping_history:
            text: str = ''
            for key in ping_object:
                text += f'{key} - {ping_object[key]}\n'

            result.append(text)

        return '\n'.join(result)


    def get_latest_ping(
        self,
        username: str,
        string_format: Optional[bool] = None
    ) -> Union[List, str]:
        """ Получаем последние выполненные пинги """

        user_object: Dict = self.get_user_object_by_username(username)
        ping_history: List[Dict] = user_object['ping_history'][-5:]

        if not string_format:
            return ping_history
        else:
            return self.change_format_ping_history(ping_history)
